﻿using System;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.Goldenlight.Resources;

namespace Microsoft.Goldenlight.Engine
{
    /// <summary>
    /// 	Defines a point in 2D space.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct Point2D : IPoint2D, IEquatable<Point2D>, IComparable<Point2D>, IComparable
    {
        #region Static Operations

        /// <summary>
        /// 	Returns the point (0,0).
        /// </summary>
        public static readonly Point2D Zero = new Point2D(0, 0);

        #region Parse

        /// <summary>
        /// 	Converts the string representation of a number to its Point2D equivalent.
        /// </summary>
        /// <returns> A Point2D that is equivalent to the numeric value or symbol specified in <paramref name="value" /> . </returns>
        public static Point2D Parse(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                throw new ArgumentNullException("value");
            }
            var parameters = value.TrimStart('(').TrimEnd(')').Split(',');
            if (parameters.Length < 2)
            {
                throw new ArgumentException(Resx.InvalidValueFormat, "value");
            }
            return new Point2D(Convert.ToInt32(parameters[0].Trim()), Convert.ToInt32(parameters[1].Trim()));
        }

        #endregion

        #region Equality & Inequality

        /// <summary>
        /// 	Returns a value that indicates whether two specified Point2D values are equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator ==(Point2D left, Point2D right)
        {
            return left._x == right._x && left._y == right._y;
        }

        /// <summary>
        /// 	Returns a value that indicates whether two specified Point2D values are equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator ==(Point2D left, IPoint2D right)
        {
            if (ReferenceEquals(right, null))
            {
                return false;
            }
            return left._x == right.X && left._y == right.Y;
        }

        /// <summary>
        /// 	Returns a value that indicates whether two specified Point2D values are not equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are not equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator !=(Point2D left, Point2D right)
        {
            return !(left == right);
        }

        /// <summary>
        /// 	Returns a value that indicates whether two specified Point2D values are not equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are not equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator !=(Point2D left, IPoint2D right)
        {
            return !(left == right);
        }

        #endregion

        #region Greater & Less Than

        /// <summary>
        /// 	Returns a value that indicates whether a specified Point2D value is greater than another specified <see
        ///  	cref="T:System.Double" /> value.
        /// </summary>
        /// <returns> true if <paramref name="left" /> is greater than <paramref name="right" /> ; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator >(Point2D left, Point2D right)
        {
            return left._x > right._x && left._y > right._y;
        }

        /// <summary>
        /// 	Returns a value that indicates whether a specified Point2D value is greater than another specified <see
        ///  	cref="T:System.Double" /> value.
        /// </summary>
        /// <returns> true if <paramref name="left" /> is greater than <paramref name="right" /> ; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator >(Point2D left, IPoint2D right)
        {
            return left._x > right.X && left._y > right.Y;
        }

        /// <summary>
        /// 	Returns a value that indicates whether a specified Point2D value is greater than or equal to another specified <see
        ///  	cref="T:System.Double" /> value.
        /// </summary>
        /// <returns> true if <paramref name="left" /> is greater than or equal to <paramref name="right" /> ; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator >=(Point2D left, Point2D right)
        {
            return left._x >= right._x && left._y >= right._y;
        }

        /// <summary>
        /// 	Returns a value that indicates whether a specified Point2D value is greater than or equal to another specified <see
        ///  	cref="T:System.Double" /> value.
        /// </summary>
        /// <returns> true if <paramref name="left" /> is greater than or equal to <paramref name="right" /> ; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator >=(Point2D left, IPoint2D right)
        {
            return left._x >= right.X && left._y >= right.Y;
        }

        /// <summary>
        /// 	Returns a value that indicates whether a specified Point2D value is less than another specified <see
        ///  	cref="T:System.Double" /> value.
        /// </summary>
        /// <returns> true if <paramref name="left" /> is less than <paramref name="right" /> ; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator <(Point2D left, Point2D right)
        {
            return left._x < right._x && left._y < right._y;
        }

        /// <summary>
        /// 	Returns a value that indicates whether a specified Point2D value is less than another specified <see
        ///  	cref="T:System.Double" /> value.
        /// </summary>
        /// <returns> true if <paramref name="left" /> is less than <paramref name="right" /> ; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator <(Point2D left, IPoint2D right)
        {
            return left._x < right.X && left._y < right.Y;
        }

        /// <summary>
        /// 	Returns a value that indicates whether a specified Point2D value is less than or equal to another specified <see
        ///  	cref="T:System.Double" /> value.
        /// </summary>
        /// <returns> true if <paramref name="left" /> is less than or equal to <paramref name="right" /> ; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator <=(Point2D left, Point2D right)
        {
            return left._x <= right._x && left._y <= right._y;
        }

        /// <summary>
        /// 	Returns a value that indicates whether a specified Point2D value is less than or equal to another specified <see
        ///  	cref="T:System.Double" /> value.
        /// </summary>
        /// <returns> true if <paramref name="left" /> is less than or equal to <paramref name="right" /> ; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator <=(Point2D left, IPoint2D right)
        {
            return left._x <= right.X && left._y <= right.Y;
        }

        #endregion

        #endregion

        #region Constructors

        /// <summary>
        /// 	Initializes a new instance of Point.
        /// </summary>
        /// <param name="x"> The x-coordinate of the Point. </param>
        /// <param name="y"> The y-coordinate of the Point. </param>
        public Point2D(int x, int y)
        {
            _x = x;
            _y = y;
        }

        /// <summary>
        /// 	Initializes a new instance of Point.
        /// </summary>
        /// <param name="point"> </param>
        public Point2D(IPoint2D point) : this(point.X, point.Y)
        {
        }

        #endregion

        #region Implementation of IPoint2D

        private int _x;

        /// <summary>
        /// 	Specifies the x-coordinate of the Point.
        /// </summary>
        public int X
        {
            get
            {
                return _x;
            }
            set
            {
                _x = value;
            }
        }

        private int _y;

        /// <summary>
        /// 	Specifies the y-coordinate of the Point.
        /// </summary>
        public int Y
        {
            get
            {
                return _y;
            }
            set
            {
                _y = value;
            }
        }

        #endregion

        #region Implementation of IComparable<in Point2D>

        /// <summary>
        /// 	Compares the current object with another object of the same type.
        /// </summary>
        /// <returns> A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the <paramref
        ///  	name="other" /> parameter.Zero This object is equal to <paramref name="other" /> . Greater than zero This object is greater than <paramref
        ///  	name="other" /> . </returns>
        /// <param name="other"> An object to compare with this object. </param>
        public int CompareTo(Point2D other)
        {
            if (this < other)
            {
                return -1;
            }
            if (this > other)
            {
                return 1;
            }
            if (this != other)
            {
                var result = _x.CompareTo(other._x);
                if (result == 0)
                {
                    result = _y.CompareTo(other._y);
                }
                return result;
            }
            return 0;
        }

        #endregion

        #region Implementation of IComparable

        /// <summary>
        /// 	Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
        /// </summary>
        /// <returns> A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes <paramref
        ///  	name="obj" /> in the sort order. Zero This instance occurs in the same position in the sort order as <paramref
        ///  	name="obj" /> .Greater than zero This instance follows <paramref name="obj" /> in the sort order. </returns>
        /// <param name="obj"> An object to compare with this instance. </param>
        /// <exception cref="T:System.ArgumentException">
        /// 	<paramref name="obj" />
        /// 	is not the same type as this instance.</exception>
        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }
            if (!(obj is Point2D))
            {
                throw new ArgumentException(Resx.MustBePoint2D, "obj");
            }
            return CompareTo((Point2D) obj);
        }

        #endregion

        #region Implementation of IEquatable<in Point2D>

        /// <summary>
        /// 	Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns> true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false. </returns>
        /// <param name="other"> An object to compare with this object. </param>
        public bool Equals(Point2D other)
        {
            return _x == other._x && _y == other._y;
        }

        #endregion

        #region Overrides of ValueType

        /// <summary>
        /// 	Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <returns> true if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, false. </returns>
        /// <param name="obj"> Another object to compare to. </param>
        public override bool Equals(object obj)
        {
            var result = false;
            if (obj is IPoint2D)
            {
                result = Equals((Point2D) obj);
            }
            return result;
        }

        /// <summary>
        /// 	Returns the hash code for this instance.
        /// </summary>
        /// <returns> A 32-bit signed integer that is the hash code for this instance. </returns>
        public override int GetHashCode()
        {
            return _x.GetHashCode() ^ _y.GetHashCode();
        }

        /// <summary>
        /// 	Returns the fully qualified type name of this instance.
        /// </summary>
        /// <returns> A <see cref="T:System.String" /> containing a fully qualified type name. </returns>
        public override string ToString()
        {
            var currentCulture = CultureInfo.CurrentCulture;
            return string.Format(currentCulture, "({0},{1})", _x.ToString(currentCulture), _y.ToString(currentCulture));
        }

        #endregion
    }
}